﻿using System;
using System.Collections.Generic;

namespace MentalAlchemy.Atomics
{
	namespace Types
	{
		#region - Histograms. -
		[Serializable]
		public class HistogramInt : List<int>
		{
			private const float MIN_VALUE = 0.0f;

			public int BinsCount
			{
				get { return Count; }
				set
				{
					Clear();
					AddRange(new int[value]);
					for (int i = 0; i < value; ++i) { this[i] = 0; }	// reset to zero.
				}
			}
			public float BinSize { get; set; }

			public void CountValue(float value)
			{
				int binIndex = (int)((value - MIN_VALUE) / BinSize);
				if (binIndex < 0) { binIndex = 0; }
				if (binIndex >= BinsCount) { binIndex = BinsCount - 1; }

				++this[binIndex];
			}

			public void CountValues(float[] values)
			{
				foreach (var value in values)
				{
					CountValue(value);
				}
			}

			/// <summary>
			/// [atomic]
			/// 
			/// Calculates sum of all counts.
			/// </summary>
			/// <returns>Sum of all counts.</returns>
			public int GetSum ()
			{
				int sum = 0;
				for (int i = 0; i < Count; ++i)
				{
					sum += this[i];
				}
				return sum;
			}

			/// <summary>
			/// [atomic]
			/// 
			/// Calculate Euclidian distance between histogram counts.
			/// </summary>
			/// <param name="hist">Reference histogram.</param>
			/// <returns>Euclidian distance value.</returns>
			public float GetDistance(HistogramInt hist)
			{
				if (Count != hist.Count) { return float.PositiveInfinity; }

				float dist = 0.0f;
				for (int i = 0; i < Count; ++i)
				{
					float temp = this[i] - hist[i];
					dist += temp * temp;
				}

				return (float)Math.Sqrt(dist);
			}

			/// <summary>
			/// [atomic]
			/// 
			/// Converts histogram to dictionary so that key = histogram bin value, and value = histogram count.
			/// </summary>
			/// <returns>Dictionary containing bins' values and counts.</returns>
			public Dictionary<float, int> ConvertToDictionary()
			{
				var res = new Dictionary<float, int>();

				var key = MIN_VALUE;
				for (int i = 0; i < BinsCount; i++, key += BinSize)
				{
					res.Add(key, this[i]);
				}

				return res;
			}

			public static int GetCountIndex(float value, int binsCount, float binSize)
			{
				int binIndex = (int)((value - MIN_VALUE) / binSize);
				if (binIndex < 0) { binIndex = 0; }
				if (binIndex >= binsCount) { binIndex = binsCount - 1; }

				return binIndex;
			}

			public static HistogramFloat Normalize(HistogramInt hist)
			{
				float sum = hist.GetSum();

				var res = new HistogramFloat();
				if (sum != 0.0f)
				{
					sum = 1.0f / sum;
					foreach (int count in hist)
					{
						res.Add(count * sum);
					}
				}
				else
				{
					res.AddRange(new float[hist.Count]);
				}

				return res;
			}

			public static float Entropy(HistogramInt hist)
			{
				HistogramFloat dHist = Normalize(hist);

				float res = 0.0f;
				foreach (var p in dHist)
				{
					if (p != 0.0)
					{
						res -= (float)(p*Math.Log(p));
					}
				}

				return res;
			}
		}

		[Serializable]
		public class HistogramFloat : List<float>
		{
			private const float MIN_VALUE = 0.0f;

			public int BinsCount
			{
				get { return Count; }
				set
				{
					Clear();
					AddRange(new float[value]);
					for (int i = 0; i < value; ++i) { this[i] = 0.0f; }	// reset to zero.
				}
			}
			public float BinSize { get; set; }

			public void CountValue(float value)
			{
				int binIndex = (int)((value - MIN_VALUE) / BinSize);
				if (binIndex < 0) { binIndex = 0; }
				if (binIndex >= BinsCount) { binIndex = BinsCount - 1; }

				this[binIndex] += 1.0f;
			}

			public void CountValues (float[] values)
			{
				foreach (var value in values)
				{
					CountValue(value);
				}
			}

			public float GetSum()
			{
				float sum = 0;
				for (int i = 0; i < Count; ++i)
				{
					sum += this[i];
				}
				return sum;
			}

			public float GetDistance(HistogramInt hist)
			{
				if (Count != hist.Count) { return float.PositiveInfinity; }

				float dist = 0.0f;
				for (int i = 0; i < Count; ++i)
				{
					float temp = this[i] - hist[i];
					dist += temp * temp;
				}

				return (float)Math.Sqrt(dist);
			}

			public static int GetCountIndex(float value, int binsCount, float binSize)
			{
				int binIndex = (int)((value - MIN_VALUE) / binSize);
				if (binIndex < 0) { binIndex = 0; }
				if (binIndex >= binsCount) { binIndex = binsCount - 1; }

				return binIndex;
			}
		}
		#endregion
	}
}